package mongotesting

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
	"testing"

	"github.com/docker/cli/cli/connhelper"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

const (
	image         = "mongo:5.0"
	containerPort = "27017/tcp"
	// ssh://<user>@<host> URL requires Docker 18.09 or later on the remote host.
	// If daemonURL != "ssh://..." use local Docker
	daemonURL = "ssh://wenzu@192.168.1.2"
)

var mongoURI string

const defaultMongoURI = "mongodb://root:123456@192.168.1.2:27018" // 携带测试数据的mongodb的地址

// DockerClientFromSSH Obtain the Docker client through SSH
func DockerClientFromSSH(ctx context.Context, daemonURL string) (*client.Client, error) {
	helper, err := connhelper.GetConnectionHelper(daemonURL)
	if err != nil {
		return nil, err
	}
	httpClient := &http.Client{
		Transport: &http.Transport{
			DialContext: helper.Dialer,
		},
	}
	cli, err := client.NewClientWithOpts(client.FromEnv,
		client.WithHTTPClient(httpClient),
		client.WithHost(helper.Host),
		client.WithDialContext(helper.Dialer),
	)
	if err != nil {
		return nil, err
	}
	cli.NegotiateAPIVersion(ctx)
	return cli, nil
}

// RunWithMongoInDocker runs the tests with
// a mongodb instance in a docker container.
func RunWithMongoInDocker(m *testing.M) int {
	var err error
	var cli *client.Client
	var hostIP, bindingHostIP string

	ctx := context.Background()

	switch urlSli := strings.SplitN(daemonURL, "://", 2); urlSli[0] {
	case "ssh":
		u, err := url.Parse(daemonURL)
		if err != nil {
			panic(err)
		}
		hostIP = u.Hostname()
		bindingHostIP = "0.0.0.0"
		cli, err = DockerClientFromSSH(context.Background(), daemonURL)
		if err != nil {
			panic(err)
		}
	default:
		hostIP = "127.0.0.1"
		bindingHostIP = hostIP

		cli, err = client.NewClientWithOpts(client.FromEnv)
		if err != nil {
			panic(err)
		}
	}

	filters := filters.NewArgs(filters.Arg("reference", image))
	images, err := cli.ImageList(context.Background(), types.ImageListOptions{Filters: filters})
	if err != nil {
		panic(err)
	}

	if len(images) == 0 {
		out, err := cli.ImagePull(context.Background(), image, types.ImagePullOptions{})
		if err != nil {
			panic(err)
		}
		defer out.Close()
		io.Copy(os.Stdout, out)
	}

	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image: image,
		ExposedPorts: nat.PortSet{
			containerPort: {},
		},
	}, &container.HostConfig{
		PortBindings: nat.PortMap{
			containerPort: []nat.PortBinding{
				{
					HostIP:   bindingHostIP,
					HostPort: "0",
				},
			},
		},
	}, nil, nil, "")
	if err != nil {
		panic(err)
	}
	containerId := resp.ID
	defer func() {
		err = cli.ContainerRemove(ctx, containerId, types.ContainerRemoveOptions{
			Force: true,
		})
		if err != nil {
			panic(err)
		}
	}()

	err = cli.ContainerStart(ctx, containerId, types.ContainerStartOptions{})
	if err != nil {
		panic(err)
	}

	inspRes, err := cli.ContainerInspect(ctx, resp.ID)
	if err != nil {
		panic(err)
	}
	hostPort := inspRes.NetworkSettings.Ports[containerPort][0]
	mongoURI = fmt.Sprintf("mongodb://%s:%s", hostIP, hostPort.HostPort)

	return m.Run()
}

// NewClient creates a client connected to the mongo instance in docker.
func NewClient(ctx context.Context) (*mongo.Client, error) {
	// 这里再做一个保护
	if mongoURI == "" {
		return nil, fmt.Errorf("mong uri not set. Please run RunWithMongoInDocker in TestMain")
	}
	return mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
}

// NewDefaultClient creates a client connected to localhost:27017
func NewDefaultClient(c context.Context) (*mongo.Client, error) {
	return mongo.Connect(c, options.Client().ApplyURI(defaultMongoURI))
}

// SetupIndexes sets up indexes for the given database.
func SetupIndexes(ctx context.Context, d *mongo.Database) error {
	_, err := d.Collection("account").Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys:    bson.D{{Key: "open_id", Value: 1}},
		Options: options.Index().SetUnique(true),
	})
	if err != nil {
		return err
	}

	_, err = d.Collection("trip").Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys: bson.D{
			{Key: "trip.accountid", Value: 1},
			{Key: "trip.status", Value: 1},
		},
		Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{
			"trip.status": 1,
		}),
	})
	if err != nil {
		return err
	}

	_, err = d.Collection("profile").Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys:    bson.D{{Key: "accountid", Value: 1}},
		Options: options.Index().SetUnique(true),
	})
	return err
}
